package com.superafroman.spring;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletContext;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ListFactoryBean;
import org.springframework.web.context.ServletContextAware;

import com.superafroman.utils.scanner.ClassScanner;
import com.superafroman.utils.scanner.criteria.ClassAnnotationCriteria;
import com.superafroman.utils.scanner.criteria.PackageNameCriteria;
import com.superafroman.utils.scanner.locater.ClasspathClassLocater;
import com.superafroman.utils.scanner.locater.WebappClassLocater;

/**
 * A Spring {@link ListFactoryBean} that looks up classes that have a certain
 * annotation and returns them as a <code>List</code>.
 */
@SuppressWarnings("unchecked")
public class AnnotatedClassListFactoryBean implements FactoryBean,
        ServletContextAware {

    private Class<?> annotation;

    private String basePackage;

    private ServletContext servletContext;

    /**
     * Set the annotation class to search for.
     *
     * @param annotation
     *         The annotation to search for.
     */
    public void setAnnotation(Class<?> annotation) {
        this.annotation = annotation;
    }

    /**
     * Set the base package to search for classes in.
     *
     * @param basePackage
     *         The base package containing the classes we're
     *         interested in.
     */
    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    /**
     * Set the servletContext so classes can be found in a web application.
     *
     * @param servletContext
     */
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    /**
     * Returns an {@link ArrayList} of classes that are within the basePackage
     * or one of the sub-packages of the basePackage.
     *
     * @return
     *         The {@link ArrayList} of classes.
     *
     * @throws Exception
     */
    public Object getObject() throws Exception {

        if (annotation == null) {
            throw new BeanCreationException("annotation must be specified.");
        }
        if (basePackage == null) {
            throw new BeanCreationException("basePackage must be specified.");
        }

        ClassScanner scanner = new ClassScanner();

        scanner.addClassLocater(new ClasspathClassLocater());
        scanner.addClassLocater(new WebappClassLocater(servletContext));

        scanner.addPackageCriteria(new PackageNameCriteria(basePackage));
        scanner.addClassCriteria(new ClassAnnotationCriteria(annotation));

        return scanner.findClasses();
    }

    public Class getObjectType() {
        return List.class;
    }

    public boolean isSingleton() {
        return true;
    }
}
